﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Diagnostics;
using AWS.SLAM;
using AWS.SLAM.Configuration;
using AWS.SLAM.Events;
using AWS.SLAM.SharePoint;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using System.Collections.Generic;
using System.Collections;
using System.Threading;

namespace AWS.SLAM
{
	/// <summary>
	/// The ListItemEventManager contains the SharePoint event handlers for slamming SPListItems based
	/// on Lists.  If a List is configured in SLAM's configuration, this is the set of events
	/// that are attached to that List in SharePoint.
	/// </summary>
	public class ListItemEventManager : SPItemEventReceiver
	{
		internal static int _runningReceivers = 0;

		/// <summary>
		/// The lock object for synchronizing Creates
		/// </summary>
		internal static readonly object _createLock = new object();

		/// <summary>
		/// The lock object for synchronizing Deletes
		/// </summary>
		internal static readonly object _deleteLock = new object();

		/// <summary>
		/// The lock object for synchronizing Updates
		/// </summary>
		internal static readonly object _updateLock = new object();

		/// <summary>
		/// The lock object for synchronizing Creates
		/// </summary>
		internal static readonly object _synchCreateLock = new object();

		/// <summary>
		/// The lock object for synchronizing Updates
		/// </summary>
		internal static readonly object _synchUpdateLock = new object();

		public override void ItemAdding(SPItemEventProperties properties)
		{
			lock (_synchCreateLock)
			{
				_runningReceivers++;
				base.ItemAdding(properties);

				try
				{
					SPWeb site = properties.OpenWeb();
					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						using (SPSite elevatedSite = new SPSite(site.Site.ID))
						{
							using (SPWeb elevatedWeb = elevatedSite.AllWebs[site.ID])
							{
								SPList elevatedList = elevatedWeb.Lists[properties.ListId];

								//Create a dummy list item
								SPListItem elevatedListItem = elevatedList.Items.Add();
								foreach (DictionaryEntry propertyValue in properties.AfterProperties)
								{
									elevatedListItem[propertyValue.Key.ToString()] = propertyValue.Value;
								}

								elevatedListItem.SlamUpdate(false, false, false, properties.UserLoginName, true);
							}
						}
					});
				}
				catch (Exception ex)
				{
					//If an Exception is bubbled up to this point, all other logging must have
					//failed in which case we will log the exception in the EventLog since this is
					//an asynchronous event handler
					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						if (!EventLog.SourceExists("SLAM"))
						{
							EventLog.CreateEventSource("SLAM", "Application");
						}
						EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
					});
				}

				if (_runningReceivers == 1)
				{
					SlamItemContext.ResetContext();
				}

				_runningReceivers--;
			}
		}

		/// <summary>
		/// Event handler executed when an SPListItem is added to a list with this handler
		/// attached.  This method calls SLAM's IICreator.Create based on this item's List.
		/// </summary>
		/// <param name="properties"></param>
		public override void ItemAdded(SPItemEventProperties properties)
		{
			lock (_createLock)
			{
				_runningReceivers++;
				base.ItemAdded(properties);

				string synchronouslyUpdated = properties.ListItem.GetSlamPropertyValue("SynchronousUpdate");
				if (String.IsNullOrEmpty(synchronouslyUpdated) || synchronouslyUpdated.Equals("false", StringComparison.OrdinalIgnoreCase))
				{
					try
					{
						properties.ListItem.SlamUpdate(true, false, false, properties.UserLoginName, true);
					}
					catch (Exception ex)
					{
						//If an Exception is bubbled up to this point, all other logging must have
						//failed in which case we will log the exception in the EventLog since this is
						//an asynchronous event handler
						SPSecurity.RunWithElevatedPrivileges(delegate()
						{
							if (!EventLog.SourceExists("SLAM"))
							{
								EventLog.CreateEventSource("SLAM", "Application");
							}
							EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
						});
					}
				}
				else
				{
					EventFiringEnabled = false;
					properties.ListItem.SetSlamProperty("SynchronousUpdate", false);
					properties.ListItem.Web.AllowUnsafeUpdates = true;
					properties.ListItem.SystemUpdate(false);
					EventFiringEnabled = true;
				}

				if (_runningReceivers == 1)
				{
					SlamItemContext.ResetContext();
				}

				_runningReceivers--;
			}
		}

		public override void ItemUpdating(SPItemEventProperties properties)
		{
			//If this same item is updated several times in quick succession,
			//make sure each SLAM Update finishes before the next one proceeds
			lock (_synchUpdateLock)
			{
				_runningReceivers++;
				base.ItemUpdating(properties);
				SPListItem listItem = properties.ListItem;
				string synchronouslyUpdated = properties.ListItem.GetSlamPropertyValue("SynchronousUpdate");
				if (String.IsNullOrEmpty(synchronouslyUpdated) || synchronouslyUpdated.Equals("false", StringComparison.OrdinalIgnoreCase))
				{
					try
					{	
						foreach (DictionaryEntry propertyValue in properties.AfterProperties)
						{
							listItem[propertyValue.Key.ToString()] = propertyValue.Value;
						}
						listItem.SlamUpdate(true, false, false, properties.UserLoginName, false);
					}
					catch (Exception ex)
					{
						//If an Exception is bubbled up to this point, all other logging must have
						//failed in which case we will log the exception in the EventLog since this is
						//an asynchronous event handler
						SPSecurity.RunWithElevatedPrivileges(delegate()
						{
							if (!EventLog.SourceExists("SLAM"))
							{
								EventLog.CreateEventSource("SLAM", "Application");
							}
							EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
						});
					}

					if (_runningReceivers == 1)
					{
						SlamItemContext.ResetContext();
					}
				}

				_runningReceivers--;
			}
		}

		/// <summary>
		/// Event handler executed when an SPListItem is updated in a list with this handler
		/// attached.  This method calls SLAM's IIUpdater.Update based on this item's List.
		/// </summary>
		/// <param name="properties"></param>
		public override void ItemUpdated(SPItemEventProperties properties)
		{
			//If this same item is updated several times in quick succession,
			//make sure each SLAM Update finishes before the next one proceeds
			lock (_updateLock)
			{
				_runningReceivers++;
				base.ItemUpdated(properties);

				try
				{
					if (properties.BeforeProperties["SLAM ID"] != null && properties.BeforeProperties["SLAM ID"] != properties.AfterProperties["SLAM ID"])
					{
						EventFiringEnabled = false;
						properties.ListItem["SLAM ID"] = properties.BeforeProperties["SLAM ID"];
						properties.Web.AllowUnsafeUpdates = true;
						properties.ListItem.SystemUpdate(false);
						EventFiringEnabled = true;
					}
				}
				catch { }

				string synchronouslyUpdated = properties.ListItem.GetSlamPropertyValue("SynchronousUpdate");
				if (String.IsNullOrEmpty(synchronouslyUpdated) || synchronouslyUpdated.Equals("false", StringComparison.OrdinalIgnoreCase))
				{
					try
					{
						properties.ListItem.SlamUpdate(true, false, false, properties.UserLoginName, false);
					}
					catch (Exception ex)
					{
						//If an Exception is bubbled up to this point, all other logging must have
						//failed in which case we will log the exception in the EventLog since this is
						//an asynchronous event handler
						SPSecurity.RunWithElevatedPrivileges(delegate()
						{
							if (!EventLog.SourceExists("SLAM"))
							{
								EventLog.CreateEventSource("SLAM", "Application");
							}
							EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
						});
					}
				}
				else
				{
					EventFiringEnabled = false;
					properties.ListItem.SetSlamProperty("SynchronousUpdate", false);
					properties.ListItem.Web.AllowUnsafeUpdates = true;
					properties.ListItem.SystemUpdate(false);
					EventFiringEnabled = true;
				}

				if (_runningReceivers == 1)
				{
					SlamItemContext.ResetContext();
				}

				_runningReceivers--;
			}
		}

		/// <summary>
		/// Event handler executed when an SPListItem is being deleted from a list with this handler
		/// attached.  This method calls SLAM's IIDeleter.Delete based on this item's List.
		/// </summary>
		/// <param name="properties"></param>
		public override void ItemDeleting(SPItemEventProperties properties)
		{
			lock (_deleteLock)
			{
				_runningReceivers++;
				base.ItemDeleting(properties);

				try
				{
					properties.Cancel = !properties.ListItem.SlamDelete(true, false, properties.UserLoginName);
					if (properties.Cancel)
					{
						properties.Status = SPEventReceiverStatus.CancelNoError;
					}
				}
				catch (Exception ex)
				{
					//If an Exception is bubbled up to this point, all other logging must have
					//failed in which case we will log the exception in the EventLog.  ItemDeleting is
					//syncrhonous unlike the other event handlers, but SLAM should not interrupt
					//a user deleting an item if it fails since SharePoint is not dependent on 
					//SLAM.
					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						if (!EventLog.SourceExists("SLAM"))
						{
							EventLog.CreateEventSource("SLAM", "Application");
						}
						EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
					});

					foreach (DictionaryEntry dataItem in ex.Data)
					{
						if (dataItem.Key.ToString() == "CancelDelete" && (bool)dataItem.Value == true)
						{
							properties.Cancel = true;
							properties.ErrorMessage = ex.Message;
							properties.Status = SPEventReceiverStatus.CancelWithError;
							break;
						}
					}
				}

				if (_runningReceivers == 1)
				{
					SlamItemContext.ResetContext();
				}

				_runningReceivers--;
			}
		}
	}
}